%% ast.erl
%%   Create simple fragments of abstract syntax trees (ASTs).
%%
%% Copyright (C) 2008, 2009 Gábor Fehér
%%
%% This file is part of ECT.
%% 
%% ECT is free software: you can redistribute it and/or modify
%% it under the terms of the GNU General Public License as published by
%% the Free Software Foundation, either version 3 of the License, or
%% (at your option) any later version.
%% 
%% ECT is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%% GNU General Public License for more details.
%% 
%% You should have received a copy of the GNU General Public License
%% along with ECT.  If not, see <http://www.gnu.org/licenses/>.
%%
-module(ast).
-export([
    element/3,
    size/2,
    setelement/4,
    hd/2,
    tl/2,
    var/2,
    var_list/2,
    match/3,
    
    list/2,
    call/3,
    rcall/4,
    mcall/4,
    dmcall/4,
    record_typecheck_guard/4,
    class_typecheck/3,
    class_type_expr/3,
    class_typecheck_guard/3
    ]).

-include("common_defs.hrl").

%% Function: var(VarName, Line)
%% Purpose: create AST fragment for a variable with a given name:
%% Generated code: VarName
var(VarName, Line) ->
    {var, Line, VarName}.

%% Function: var_list(N, Line)
%% Purpose: create AST fragment for a list of variables, all of them with distinct names
%% Src of result: ['VarN', ..., 'Var2', 'Var1']
var_list(N, _) when N == 0 ->
    [];
var_list(N, Line) when N > 0 ->
    [{var,Line, list_to_atom("Var"++integer_to_list(N))}|var_list(N-1, Line)].

%% Function: match(Dest, Src, Line)
%% Purpose: create AST fragment for a match:
%% Generated code: Dest = Src
match(Dest, Src, Line) ->
    {match, Line, Dest, Src}.

%% Function: element(Pos, Tuple, Line)
%% Purpose: create AST fragment for querying an element of a tuple
%% Generated code: element(Pos, Tuple)
element(Pos, Tuple, Line) ->
    call(element, [{integer, Line, Pos}, Tuple], Line).

%% Function: size(Tuple, Line)
%% Purpose: create AST fragment for querying the size of a tuple
%% Generated code: size(Tuple)
size(Tuple, Line) ->
    call(tuple_size, [Tuple], Line).

%% Function: setelement(Pos, Tuple, E, Line)
%% Purpose: create AST fragment for updating an element of a tuple
%% Generated code: setelement(Pos, Tuple, E)
setelement(Pos, Tuple, E, Line) ->
    call(setelement, [{integer, Line, Pos}, Tuple, E], Line).

%% Function: hd(List, Line)
%% Purpose: create AST fragment for querying the head of a list
%% Generated code: hd(List)
hd(List, Line) ->
    call(hd, [List], Line).

%% Function: tl(List, Line)
%% Purpose: create AST fragment for querying the tail of a list
%% Generated code: tl(List)
tl(List, Line) ->
    call(tl, [List], Line).

%% Function: call(Function, Params, Line)
%% Purpose: create AST fragment for calling a local function (in the same module as the caller)
%% Generated code: Function(Params)
call(Function, Params, Line) ->
    {call, Line, {atom, Line, Function}, Params}.

%% Function: rcall(Module, Function, Params, Line)
%% Purpose: create AST fragment for calling a remote function (qualifying the call with module name)
%% Generated code: Module:Function(Params)
rcall(Module, Function, Params, Line) ->
    {call, Line, {remote, Line, {atom, Line, Module}, {atom, Line, Function}}, Params}.

%% Function: mcall(Object, Function, Params, Line)
%% Purpose: create AST fragment for calling a method of an object
%% Generated code: {Object}:Function(Params)
mcall(Object, Function, Params, Line) ->
    {call, Line,
        {remote, Line, element(1, Object, Line), {atom, Line, Function}},
        [Object|Params]}.

%% Function: dmcall(Object, Function, Params, Line)
%% Purpose: create AST fragment for calling a method of an object
%%          each argument of this function is a variable name, so their values 
%%          only reveal at the run-time of the generated code 
%% Generated code: apply(element(1, Object)), Function, Params)
dmcall(Object, Method, Params, Line) ->
    call(
        apply,
        [
            element(1, {var, Line, Object}, Line),
            {var, Line, Method},
            {cons, Line,
                {var, Line, Object},
                {var, Line, Params}
            }
        ],
        Line).

%% Function: record_typecheck_guard(Records, Name, Expr, Line)
%% Purpose: create AST fragment that is a guard checking the type of a record
%% Generated code: is_record(Expr, Name, NNN) where NNN is the size of the record type specified by name
record_typecheck_guard(Records, Name, Expr, Line) ->
    {record, _, Fields} = records:lookup(Records, Name),
    call(is_record, [Expr, {atom, Line, Name}, {integer, Line, length(Fields)+1}], Line).

%% Function: list(List, Line)
%% Purpose: create AST fragment that represents a list
%% Generated code: [Item1, Item2, ...]
%% Note that the AST fragment for a list is not the list itself.
list([], Line) ->
    {nil, Line};
list([A|L], Line) ->
    {cons, Line, A, list(L, Line)}.
    
%% Function: class_type_expr(Definition, Expr, Line)
%% Purpose: create AST fragment that returns the class at a given position of a given object
%% Generated code: element(1, element(NNN, Object))
%%                 where NNN is the depth of class defined by Definition in the inheritance chain
class_type_expr(#class{class_level = TypeLevel}, Expr, Line) ->
    element(1, element(TypeLevel+?FIELD_GLOBAL_STARTPOS-1, Expr, Line), Line).

%% Function: class_typecheck_guard(Definition, Expr, Line)
%% Purpose: create AST fragment that is guard for checking an object to have a certain class (defined by Definition)
%% Generated code: ClassName =:= element(1, element(NNN, Object))
%%                 where NNN is the depth of class defined by Definition in the inheritance chain
%%                 and ClassName is extracted from Definition
class_typecheck_guard(Definition, Expr, Line) ->
    class_typecheck_guard0(Definition, Expr, '=:=', Line).
class_typecheck_guard0(Definition = #class{name = Type}, Expr, Op, Line) ->
    {op, Line, Op,
        {atom, Line, Type}, 
        class_type_expr(Definition, Expr, Line)
    }.

%% Function: class_typecheck_guard(Definition, Expr, Line)
%% Purpose: create AST fragment that is an expression for checking an object to have a certain class (defined by Definition)
%% Generated code: ClassName = element(1, element(NNN, Object))
%%                 where NNN is the depth of class defined by Definition in the inheritance chain
%%                 and ClassName is extracted from Definition
%% Note: generated code raises error if Expr have the wrong class, continues otherwise
class_typecheck(Definition = #class{name = Type}, Expr, Line) ->
    {match, Line, 
        {atom, Line, Type}, 
        class_type_expr(Definition, Expr, Line)
    }.


